<template lang="">
  <div v-loading="graphLoading" id="container" class="architect-graph-container"></div>
  <div class="right-menu">
    <el-card
      style="height: 100%;"
      shadow="always"
      :body-style="{ padding: '0px', height: '90%' }"
    >
      <template #header>
        <div class="card-header">
          <span>架构图配置</span>
        </div>
      </template>
      <div style="text-align: left; height: 100%">
        <el-tabs
          stretch
          type="border-card"
          style="box-shadow: none; border-width: 1px 0 0 0; height: 100%;"
        >
          <el-tab-pane label="布局配置">
            <el-row v-for="(layoutOptionConfig, optionName) in layoutOptionMenuConfig">
              <div class="menu-item" v-if="layoutOptionConfig.type === 'select'">
                <span class="menu-item-label">{{layoutOptionConfig.label}}：</span>
                <el-select v-model="layoutOption[optionName]" style="width: 150px; margin-bottom: 10px" size="large" @change="layoutModeChange">
                  <el-option v-for="item in layoutOptionConfig.options" :key="item.value" :label="item.label"
                    :value="item.value">
                  </el-option>
                </el-select>
              </div>
              <div class="menu-item" v-if="layoutOptionConfig.type === 'input-number'">
                <span class="menu-item-label">{{layoutOptionConfig.label}}：</span>
                <el-input-number v-model="layoutOption[optionName]" :min="layoutOptionConfig.min" :max="layoutOptionConfig.max"
                  :step="layoutOptionConfig.step">
                </el-input-number>
              </div>
            </el-row>
          </el-tab-pane>
          <el-tab-pane label="图标配置">
            <el-row>
              <div class="menu-item" v-for="node in visualGraph.nodes" @mouseenter="mouseEnterIconConfig(node)" @mouseleave="mouseLeaveIconConfig(node)">
                <span class="menu-item-label" :title="node.name" style="width:90px;font-size:14px; text-overflow: ellipsis; white-space: nowrap;">
                  {{node.name}}：
                </span>
                <el-select 
                  style="width: 150px; margin-bottom: 10px;margin-left:10px" size="large" 
                  v-model="node.attrs.type" 
                  placeholder="选择图标" 
                  @change="rectIconChange"
                >
                  <template  v-slot:prefix>
                    <svg style="width: 30px;height: 30px">
                      <use :href="`#svg-icon-${node.attrs.type}`"></use>
                    </svg>
                  </template>
                  <el-option
                    v-for="svg in svgList"
                    :key="svg"
                    :label="svg"
                    :value="svg"
                  >
                    <svg style="width: 30px;height: 30px">
                      <use :href="`#svg-icon-${svg}`"></use>
                    </svg>
                    <span style="float: right;color: var(--el-text-color-secondary);font-size: 13px;">
                      {{ svg }}
                    </span>
                  </el-option>
                </el-select>
              </div>
            </el-row>
          </el-tab-pane>
        </el-tabs>
      </div>
    </el-card>
  </div>
</template>
   
<style lang="scss">
.el-tab-pane {
  height: calc(100vh - 140px);
  overflow-y: auto;
}
/** transparent menu that fixed at bottom */
.bottom-menu {
  position: fixed;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 60px;
  padding-top: 10px;
  text-align: center;
}

/** transparent menu that fixed at right */
.right-menu {
  position: fixed;
  top: 0;
  right: 0;
  width: 320px;
  height: 100%;
  text-align: center;
  // border: 1px solid #ebeef5;
  // background-color: #fff;
}

.menu-item {
  margin-top: 20px;
}

.menu-item-label {
  display: inline-block;
  width: 100px;
  padding-left: 20px;
  overflow: hidden;
}

/** full screen calc */
.architect-graph-container {
  width: 100%;
  height: 100vh;
}

.x6-edge:hover path:nth-child(2) {
  stroke: #1890ff;
  stroke-width: 1.5px;
}

.x6-edge-selected path:nth-child(2) {
  stroke: #1890ff;
  stroke-width: 2px !important;
}

@keyframes running-line {
  to {
    stroke-dashoffset: -1000;
  }
}

@keyframes spin {
  from {
    transform: rotate(0deg);
  }

  to {
    transform: rotate(360deg);
  }
}
</style>

<script setup lang="ts">
import { onMounted, ref, watch, reactive, toRaw } from 'vue'
import { Edge, Graph, Node } from '@antv/x6';
import GraphNode from './graph-node/index.vue'
import '@antv/x6-vue-shape'
import { buildGraph, NodeInput, EdgeInput } from '../../util/graph'
import graphSample from '../../assets/graph-sample.json';
import { genDAG, LayoutOption, LayoutMode } from '../../algorithm/dag-layout';
import { offset } from '@popperjs/core';
import { VisualGraph, VisualGraphNode } from '../../model/graph';
const svgList = ['消息队列', '应用服务', '应用集群', '中间件服务', '路由', '数据库', '数据库集群', '网络存储', '云计算', '云存储', '云服务', '监控', '客户端', '用户', '三方', '默认']

// get graph data from sessionStorage, if not exist, use graphSample
const sourceData = sessionStorage.getItem('graphData') ? JSON.parse(sessionStorage.getItem('graphData')!) : graphSample

const graphLoading = ref(false)
const mouseEnterIconConfig = (node) => {
  // console.log(node)
  const targetNode = graph?.getCellById(node.id)
  targetNode?.setData({ status: 'highlight' });
}
const mouseLeaveIconConfig = (node) => {
  console.log(node)
  const targetNode = graph?.getCellById(node.id)
  targetNode?.setData({ status: '' });
}

const layoutOption = reactive<LayoutOption>({
  mode: LayoutMode.Horizontal,
  levelInterval: 300,
  nodeMinInterval: 150,
  selectedRoot: undefined,
  useMaxNodeLevel: false,    // DFS or BFS
  layoutOptimize: true,
  edgePortEnabled: true,
  staggerOffset: 10,
  edgeLabelEnabled: true,
  useDirection: false,
  nodeDisplayType: 'svg',
})

const layoutOptionMenuConfig: any = {
  mode: {
    label: '方向',
    type: 'select',
    options: [
      { label: '水平', value: LayoutMode.Horizontal },
      { label: '垂直', value: LayoutMode.Vertical },
    ]
  },
  useMaxNodeLevel: {
    label: '节点深度',
    type: 'select',
    options: [
      { label: '最小深度', value: false },
      { label: '最大深度', value: true },
    ]
  },
  layoutOptimize: {
    label: '布局优化',
    type: 'select',
    options: [
      { label: '是', value: true },
      { label: '否', value: false },
    ]
  },
  levelInterval: {
    label: '层间距',
    type: 'input-number',
    min: 0,
    max: 10000,
    step: 50,
  },
  nodeMinInterval: {
    label: '节点间距',
    type: 'input-number',
    min: 0,
    max: 10000,
    step: 50,
  },
  edgePortEnabled: {
    label: '边端点区分',
    type: 'select',
    options: [
      { label: '是', value: true },
      { label: '否', value: false },
    ]
  },
  staggerOffset: {
    label: '节点错开',
    type: 'input-number',
    min: 0,
    max: 10000,
    step: 5,
  },
  edgeLabelEnabled: {
    label: '边标签',
    type: 'select',
    options: [
      { label: '是', value: true },
      { label: '否', value: false },
    ]
  },
  nodeDisplayType: {
    label: '节点样式',
    type: 'select',
    options: [
      { label: '大图标', value: 'svg' },
      { label: '矩形', value: 'rect' },
    ]
  },
}

let lastMode = layoutOption.mode
const layoutModeChange = (mode: LayoutMode) => {
  if (mode === lastMode) {
    return
  } else {
    lastMode = mode
  }
  if (mode === LayoutMode.Horizontal) {
    console.log('horizontal')
    layoutOption.levelInterval = 300
    layoutOption.nodeMinInterval = 150
  } else {
    console.log('vertical')
    layoutOption.levelInterval = 150
    layoutOption.nodeMinInterval = 300
  }
  // console.log(toRaw(layoutOption))
}

let graph = null as Graph | null
// watch layoutOption, and update graph layout
watch(layoutOption, async (cur, prev) => {
  console.log(toRaw(cur))
  if (!!graph) {
    graphLoading.value = true
    layoutModeChange(layoutOption.mode)
    // graph.dispose()
    // initGraph()
    await graph.clearCells()
    updateGraph()
    graphLoading.value = false
  }
}, { deep: true })

const rectIconChange = async () => {
  graphLoading.value = true
  await graph?.clearCells()
  updateGraph()
  graphLoading.value = false
}

Graph.registerNode("dag-node-rect", {
  inherit: "vue-shape",
  width: 160,
  height: 40,
  component: GraphNode
});

Graph.registerNode("dag-node-svg", {
  inherit: "vue-shape",
  width: 60,
  height: 40,
  component: GraphNode
});

Graph.registerEdge(
  'dag-edge',
  {
    inherit: 'edge',
    router: {
      name: 'manhattan',
    },
    attrs: {
      line: {
        stroke: '#C2C8D5',
        strokeWidth: 1,
        targetMarker: {
          name: 'classic',
          size: 6
        },
      },
    },
  },
  // true,
);

const initGraph = () => {
  graph = new Graph({
    container: document.getElementById("container") as HTMLElement,
    mousewheel: {
      enabled: true,
      modifiers: ['ctrl', 'meta'],
    },
    scroller: {
      enabled: true,
      pannable: true,
      autoResize: true,
    },
    selecting: {
      enabled: true,
      multiple: true,
      rubberEdge: true,
      rubberNode: true,
      modifiers: 'shift',
      rubberband: true,
    },
    connecting: {
      snap: true,
      allowBlank: false,
      allowLoop: false,
      highlight: true,
      // connector: 'algo-connector',
      // connectionPoint: 'anchor',
      // anchor: 'center',
      // validateMagnet({ magnet }) {
      //   return magnet.getAttribute('port-group') !== 'top'
      // },
      // createEdge() {
      //   return graph.createEdge({
      //     shape: 'dag-edge',
      //     attrs: {
      //       line: {
      //         strokeDasharray: '5 5',
      //       },
      //     },
      //     zIndex: -1,
      //   })
      // },
    },
    highlighting: {
      magnetAdsorbed: {
        name: 'stroke',
        args: {
          attrs: {
            fill: '#fff',
            stroke: '#31d0c6',
            strokeWidth: 4,
          },
        },
      },
    },
    // width: 600,
    // height: 400,
    // grid: true,
  });
  return graph;
}

const visualGraph = ref<VisualGraph>(VisualGraph.blank())
const updateGraph = () => {
  if (!graph) return
  // build graph from assets/graph-sample.json
  const graphData = buildGraph(sourceData.nodes as NodeInput[], sourceData.edges as EdgeInput[]);
  console.log(graph)
  // convert to visual dag graph
  visualGraph.value = genDAG(graphData, layoutOption);
  const visualGraphData = visualGraph.value;
  // layoutDAG(visualGraphData, new LayoutOption());
  console.log(visualGraphData)

  const nodeIdToGraphNode = new Map<string, Node>();
  // random -staggerOffset ~ staggerOffset
  // const randNum = () => Math.random() * layoutOption.staggerOffset - layoutOption.staggerOffset / 2;
  const offset_x = Math.abs(Math.min(visualGraphData.minX(), 0)) + 20;
  const offset_y = Math.abs(Math.min(visualGraphData.minY(), 0)) + 50;
  const nodeLevelCount = new Array<number>(visualGraphData.levelMap.size).fill(0);
  for (const node of visualGraphData.nodes) {
    const newNode = graph.addNode({
      id: node.id,
      shape: 'dag-node-' + layoutOption.nodeDisplayType,
      x: offset_x + node.x + (layoutOption.mode === LayoutMode.Horizontal && layoutOption.staggerOffset !== 0 ? layoutOption.staggerOffset * nodeLevelCount[node.level]++ : 0),
      y: offset_y + node.y + (layoutOption.mode === LayoutMode.Vertical && layoutOption.staggerOffset !== 0 ? layoutOption.staggerOffset * nodeLevelCount[node.level]++ : 0),
      // width: 160,
      // height: 40,
      data: {
        label: (layoutOption.edgeLabelEnabled ? `[${node.id}] ` : '') + node.name,
        status: '',
        type: node.attrs.type,
        nodeType: layoutOption.nodeDisplayType,
        layoutMode: layoutOption.mode,
      },
      ports: {
        groups: {
          // 输入链接桩群组定义
          left: {
            position: 'left',
            attrs: {
              circle: {
                r: 3,
                magnet: true,
                stroke: 'grey',
                // strokeWidth: 2,
                // fill: '#fff',
              },
            },
          },
          right: {
            position: 'right',
            attrs: {
              circle: {
                r: 3,
                magnet: true,
                stroke: 'grey',
              },
            },
          },
          top: {
            position: 'top',
            attrs: {
              circle: {
                r: 3,
                magnet: true,
                stroke: 'grey',
              },
            },
          },
          bottom: {
            position: 'bottom',
            attrs: {
              circle: {
                r: 3,
                magnet: true,
                stroke: 'grey',
              },
            },
          },
        },
        items: []
      }
    });
    nodeIdToGraphNode.set(node.id, newNode);
  }

  for (const edge of visualGraphData.edges) {
    // add source and target ports
    if (layoutOption.edgePortEnabled) {
      const sourceNode = visualGraphData.nodes.find(node => node.id === edge.source.id) as VisualGraphNode;
      const targetNode = visualGraphData.nodes.find(node => node.id === edge.target.id) as VisualGraphNode;
      const sourceDisplayNode = nodeIdToGraphNode.get(edge.source.id) as Node;
      const targetDisplayNode = nodeIdToGraphNode.get(edge.target.id) as Node;
      sourceDisplayNode.addPort({
        id: `${edge.source.id}->${edge.target.id}`,
        group: sourceNode?.level > targetNode?.level ? (layoutOption.mode === LayoutMode.Horizontal ? 'left' : 'top') : (layoutOption.mode === LayoutMode.Horizontal ? 'right' : 'bottom'),
      });
      targetDisplayNode.addPort({
        id: `${edge.source.id}->${edge.target.id}`,
        group: sourceNode?.level < targetNode?.level ? (layoutOption.mode === LayoutMode.Horizontal ? 'left' : 'top') : (layoutOption.mode === LayoutMode.Horizontal ? 'right' : 'bottom'),
      });
    }
    const newEdge = graph.addEdge({
      shape: "dag-edge",
      source: {
        cell: edge.source.id,
        port: layoutOption.edgePortEnabled ? `${edge.source.id}->${edge.target.id}` : undefined,
      },
      target: {
        cell: edge.target.id,
        port: layoutOption.edgePortEnabled ? `${edge.source.id}->${edge.target.id}` : undefined,
      },
      id: edge.id,
      defaultLabel: {
        markup: [
          // {
          //   tagName: "ellipse",
          //   selector: "bg"
          // },
          {
            tagName: "text",
            selector: "txt"
          }
        ],
        attrs: {
          txt: {
            fill: "#7c68fc",
            fontSize: 12,
            textAnchor: "middle",
            // textVerticalAnchor: "middle"
          },
          // bg: {
          //   ref: "txt",
          //   refRx: "70%",
          //   refRy: "80%",
          //   // stroke: "#7c68fc",
          //   fill: "white",
          //   // strokeWidth: 2
          // }
        }
      }
    });
    // labelCountMap.set(`${edge.source}`, 0)
    // const offset_y = -20 + 20 * label_count
    if (layoutOption.edgeLabelEnabled) {
      newEdge.appendLabel({
        attrs: {
          txt: {
            /** source -> target */
            text: edge.source.id + '->' + edge.target.id
            // text: 'sss'
          }
        },
        position: {
          distance: 0.5,
          offset: {
            // x: -40,
            y: -15,
          },
        }
      })
    }
  }
  // graph.fitToContent()
  // graph.translate(100, 100)
}

onMounted(async () => {
  graphLoading.value = true
  initGraph()
  updateGraph()
  // wait for 3s(synced)
  // await new Promise(resolve => setTimeout(resolve, 3000))
  graphLoading.value = false
})


</script>